
#include "BfcpStack.hxx"
#include "BfcpTransportEventThread.hxx"
#include "BfcpUdpTransport.hxx"
#include "BfcpMessage.hxx"
#include "bfcp_message_processing/bfcp_strings.h"

#include "rutil/DnsUtil.hxx"
#include "zklog/zklog_wrapper.h"

using namespace resip;


namespace bfcp
{
    unsigned short BfcpStack::sNextTransactionId = 1;
    unsigned short BfcpStack::sNextFloorRequestId = 1;

    BfcpStack::BfcpStack(FdPollGrp* grp/* = 0*/)
        : mPollGrp(grp ? grp : FdPollGrp::create(/*auto detach*/))
        , mPollGrpIsMine(!grp)
        , mTransport(0)
        , mEventThread(0)
        , mInternalThreadsRunning(false)
    {
    }

    BfcpStack::~BfcpStack()
    {
        shutdownInternal();
        if (mEventThread)
        {
            delete mEventThread;
            mEventThread = 0;
        }

        if (mPollGrpIsMine)
        {
            delete mPollGrp;
            mPollGrp = 0;
        }

        if (mTransport)
        {
            delete mTransport;
            mTransport = 0;
        }
    }

    void BfcpStack::addTransport(resip::TransportType protocol,
        resip::IpVersion version,
        const resip::Data& ipInterface,
        int port)
    {
        resip_assert(!mTransport);

        try
        {
            // If address is specified, ensure it is valid
            if (!ipInterface.empty())
            {
                if (version == V6)
                {
                    if (!DnsUtil::isIpV6Address(ipInterface))
                    {
                        ZKLOG_ERROR << "Failed to create transport, invalid ipInterface specified (IP address required): V6 "
                            << BfcpNetTuple::toData(protocol) << " " << port << " on "
                            << ipInterface.c_str();
                        throw BfcpTransportIf::Exception("Invalid ipInterface specified (IP address required)", __FILE__, __LINE__);
                    }
                }
                else // V4
                {
                    if (!DnsUtil::isIpV4Address(ipInterface))
                    {
                        ZKLOG_ERROR << "Failed to create transport, invalid ipInterface specified (IP address required): V4 "
                            << BfcpNetTuple::toData(protocol) << " " << port << " on "
                            << ipInterface.c_str();
                        throw BfcpTransportIf::Exception("Invalid ipInterface specified (IP address required)", __FILE__, __LINE__);
                    }
                }
            }

            if (protocol == resip::UDP)
            {
                mTransport = new BfcpUdpTransport(ipInterface, port, version, mStateMacFifo);
            }
            else
            {
                ZKLOG_CRITICAL << "Can't add unknown transport.";
                throw BfcpTransportIf::Exception("Can't add unknown transport.", __FILE__, __LINE__);
            }
        }
        catch (resip::BaseException* e)
        {
            throw BfcpTransportIf::Exception(e->what(), __FILE__, __LINE__);
        }
    }

    int BfcpStack::getPort() const
    {
        return mTransport->getPort();
    }

    const bfcp::BfcpNetTuple& BfcpStack::getTuple() const
    {
        return mTransport->getTuple();
    }
    
    void BfcpStack::process(BfcpTransactionMessage* bfcpMsg)
    {
        BfcpMessage* msg = dynamic_cast<BfcpMessage*>(bfcpMsg);
        if (msg)
        {
            const bfcp_received_message_error* err = msg->getErrors();
            if (err)
            {
                ZKLOG_INFO << "Invalid Bfcp Package Incoming!";

                switch (err->code)
                {
                case BFCP_PARSING_WRONG_VERSION:
                    responseError(msg, BFCP_UNSUPPORTED_VERSION);
                    break;
                case BFCP_PARSING_RESERVED_NOT_ZERO:
                    responseError(msg, BFCP_GENERIC_ERROR);
                    break;
                case BFCP_PARSING_UNKNOWN_PRIMITIVE:
                    responseError(msg, BFCP_UNKNOWN_PRIMITIVE);
                    break;
                case BFCP_PARSING_UNKNOWN_ATTRIBUTE:
                    // here process as BFCP_UNABLE_TO_PARSE_MESSAGE or deal with it in a very complex way
                    responseError(msg, BFCP_UNABLE_TO_PARSE_MESSAGE);
                    break;
                case BFCP_PARSING_WRONG_LENGTH:
                    responseError(msg, BFCP_INCORRECT_MESSAGE_LENGTH);
                    break;
                case BFCP_PARSING_ERROR:
                    responseError(msg, BFCP_UNABLE_TO_PARSE_MESSAGE);
                    break;
                default:
                    break;
                }
                return;
            }
            else
            {
                ZKLOG_INFO << "Incoming bfcp message, brief:\n" << msg->brief();
                //InfoLog(<< "Incoming bfcp message, brief:" << std::endl << msg->brief());

//                 if (mTransactions.find(msg->getTransactionId()) != mTransactions.end())
//                 {
//                     ZKLOG_WARNING << "Received BFCP message which transaction id exist already! ignore";
//                     return;
//                 }
//                 else
//                 {// stored this bfcp incoming message;
//                     mTransactions[msg->getTransactionId()] = msg;
//                 }

                switch (msg->getPrimitive())
                {
                case ErrorAck:
                    // delete request transaction
                    return;
                case FloorRequestStatusAck:
                    // delete request transaction
                    return;
                }

                //processIncoming(msg);
                processIncoming(bfcpMsg);

//                 switch (msg->getPrimitive())
//                 {
//                 case Hello:
//                     responseHelloAck(msg);
//                 case ErrorAck:
//                     break;
//                 }
            }
        }
        else
        {
            return;
        }
    }

    void BfcpStack::processOngoing(BfcpMessage* msg)
    {
        msg->flush();
        send(std::auto_ptr<BfcpSendData>(new BfcpSendData(msg->copyRawData(), msg->getDestination())));
        ZKLOG_INFO << "Ongoing bfcp message, brief:\n" << msg->brief();
    }

    void BfcpStack::send(std::auto_ptr<BfcpSendData> data)
    {
        mTransport->send(data);
    }

    std::auto_ptr<bfcp::BfcpMessage> BfcpStack::requestHello(const bfcp::BfcpNetTuple& destination, unsigned int cid, unsigned short uid)
    {
        std::auto_ptr<BfcpMessage> req = makeRequest(destination, Hello, cid, uid, sNextTransactionId++);
        processOngoing(req.get());

        return req;
    }

    std::auto_ptr<BfcpMessage> BfcpStack::requestFloorStatus(const bfcp::BfcpNetTuple& destination,
        unsigned int cid, unsigned short uid,
        unsigned short floor_id, unsigned short status)
    {
        return requestFloorStatus(destination, cid, uid, sNextTransactionId++, floor_id, sNextFloorRequestId, status);
    }

    std::auto_ptr<BfcpMessage> BfcpStack::requestFloorStatus(const bfcp::BfcpNetTuple & destination,
        unsigned int cid, unsigned short uid, unsigned short tid,
        unsigned short floor_id, unsigned short floor_req_id, unsigned short status)
    {
        std::auto_ptr<BfcpMessage> req = makeRequest(destination, FloorStatus, cid, uid, tid);

        req->arguments()->fID = bfcp_new_floor_id_list(floor_id, 0);

        bfcp_floor_request_status* fRS = bfcp_new_floor_request_status(floor_id, status, 0, NULL);
        bfcp_overall_request_status* oRS = bfcp_new_overall_request_status(floor_req_id, status, 0, NULL);
        req->arguments()->frqInfo = bfcp_new_floor_request_information(floor_req_id, oRS, fRS, NULL, NULL, BFCP_NORMAL_PRIORITY, NULL);

        processOngoing(req.get());

        return req;
    }

    std::auto_ptr<BfcpMessage> BfcpStack::requestFloorRequestStatus(const bfcp::BfcpNetTuple& destination,
        unsigned int cid, unsigned short uid,
        unsigned short floor_id, unsigned short status)
    {
        return requestFloorRequestStatus(destination, cid, uid, sNextTransactionId++, floor_id, sNextFloorRequestId, status);
    }

    std::auto_ptr<BfcpMessage> BfcpStack::requestFloorRequestStatus(const bfcp::BfcpNetTuple& destination,
        unsigned int cid, unsigned short uid, unsigned short tid,
        unsigned short floor_id, unsigned short floor_req_id, unsigned short status)
    {
        std::auto_ptr<BfcpMessage> req = makeRequest(destination, FloorRequestStatus, cid, uid, tid);

        bfcp_floor_request_status* fRS = bfcp_new_floor_request_status(floor_id, status, 0, NULL);
        bfcp_overall_request_status* oRS = bfcp_new_overall_request_status(floor_req_id, status, 0, NULL);
        req->arguments()->frqInfo = bfcp_new_floor_request_information(floor_req_id, oRS, fRS, NULL, NULL, BFCP_NORMAL_PRIORITY, NULL);

        processOngoing(req.get());

        return req;
    }

    std::auto_ptr<bfcp::BfcpMessage> BfcpStack::requestGoodbye(const bfcp::BfcpNetTuple& destination, unsigned int cid, unsigned short uid)
    {
        std::auto_ptr<BfcpMessage> req = makeRequest(destination, Goodbye, cid, uid, sNextTransactionId++);
        processOngoing(req.get());

        return req;
    }

    std::auto_ptr<BfcpMessage> BfcpStack::responseError(const bfcp::BfcpMessage* req, unsigned short code, const char* err_info/* = NULL */)
    {
        resip_assert(!(code < BFCP_CONFERENCE_DOES_NOT_EXIST || code > BFCP_AUTHENTICATION_FAILED));

        std::auto_ptr<BfcpMessage> resp = makeResponse(req, ErrorBfcp);
        resp->arguments()->error = bfcp_new_error(code, NULL);

        if (!err_info)
        {
            err_info = bfcp_error_type[code - 1].description;
        }
        resp->arguments()->eInfo = (char*)calloc(1, strlen(err_info) * sizeof(char) + 1);
        memcpy(resp->arguments()->eInfo, err_info, strlen(err_info) + 1);

        processOngoing(resp.get());

        return resp;
    }

    std::auto_ptr<BfcpMessage> BfcpStack::responseHelloAck(const bfcp::BfcpMessage* req)
    {
        std::auto_ptr<BfcpMessage> resp = makeResponse(req, HelloAck);

        /* Create a list of all the primitives the FCS supports */
        resp->arguments()->primitives = bfcp_new_supported_list(
            FloorRequest,
            FloorRelease,
            FloorRequestQuery,
            FloorRequestStatus,
            FloorQuery,
            FloorStatus,
            Hello,
            HelloAck,
            ErrorBfcp,
            FloorRequestStatusAck,
            ErrorAck,
            FloorStatusAck, 0);
        /* Create a list of all the attributes the FCS supports */
        resp->arguments()->attributes = bfcp_new_supported_list(
            FLOOR_ID,
            FLOOR_REQUEST_ID,
            REQUEST_STATUS,
            ERROR_CODE,
            ERROR_INFO,
            FLOOR_REQUEST_INFORMATION,
            REQUESTED_BY_INFORMATION,
            FLOOR_REQUEST_STATUS,
            OVERALL_REQUEST_STATUS,
            NONCE,
            DIGEST, 0);

        processOngoing(resp.get());

        return resp;
    }

    std::auto_ptr<BfcpMessage> BfcpStack::responseFloorRequestStatus(const bfcp::BfcpMessage* req, unsigned short status)
    {
        return responseFloorRequestStatus(req, sNextFloorRequestId, status);
    }

    std::auto_ptr<BfcpMessage> BfcpStack::responseFloorRequestStatus(const bfcp::BfcpMessage* req, unsigned short floor_req_id, unsigned short status)
    {
        std::auto_ptr<BfcpMessage> resp = makeResponse(req, FloorRequestStatus);
        unsigned short floor_id = req->getParsedMsg()->arguments->fID->ID;

        bfcp_floor_request_status* fRS = bfcp_new_floor_request_status(floor_id, status, 0, NULL);
        bfcp_overall_request_status* oRS = bfcp_new_overall_request_status(floor_req_id, status, 0, NULL);
        resp->arguments()->frqInfo = bfcp_new_floor_request_information(floor_req_id, oRS, fRS, NULL, NULL, BFCP_NORMAL_PRIORITY, NULL);

        processOngoing(resp.get());

        return resp;
    }

    std::auto_ptr<bfcp::BfcpMessage> BfcpStack::responseFloorStatusAck(const bfcp::BfcpMessage* req)
    {
        std::auto_ptr<BfcpMessage> resp = makeResponse(req, FloorStatusAck);
        processOngoing(resp.get());

        return resp;
    }

    std::auto_ptr<bfcp::BfcpMessage> BfcpStack::responseGoodbyeAck(const bfcp::BfcpMessage* req)
    {
        std::auto_ptr<BfcpMessage> resp = makeResponse(req, GoodbyeAck);
        processOngoing(resp.get());

        return resp;
    }

    std::auto_ptr<BfcpMessage> BfcpStack::makeResponse(const BfcpMessage* request, int primitive)
    {
        BfcpMessage* resp = new BfcpMessage(primitive, request->getConferenceId(), request->getUserId(), request->getTransactionId(), false);
        resp->setDestination(request->getSource());
        return std::auto_ptr<BfcpMessage>(resp);
    }

    std::auto_ptr<BfcpMessage> BfcpStack::makeRequest(const BfcpNetTuple& destination, int primitive, unsigned int cid, unsigned short uid, unsigned short tid)
    {
        BfcpMessage* req = new BfcpMessage(primitive, cid, uid, tid);
        req->setDestination(destination);
        return std::auto_ptr<BfcpMessage>(req);
    }

    void BfcpStack::runInternal()
    {
        if (mInternalThreadsRunning)
            return;

        delete mEventThread;
        mEventThread = new BfcpTransportEventThread(*this, *mPollGrp);
        mEventThread->run();
        mInternalThreadsRunning = true;
    }

    void BfcpStack::shutdownInternal()
    {
        if (mEventThread)
        {
            mEventThread->shutdown();
            mEventThread->join();
            mInternalThreadsRunning = false;
        }
    }

    void BfcpStack::setPollGrp(FdPollGrp *grp)
    {
        mTransport->setPollGrp(grp);
    }

    void BfcpStack::process()
    {
        mTransport->process();
    }


}


